<?php

/**
 * Maikuro Framework
 *
 * LICENSE:
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * The license is bundled with this package in the file LICENSE.txt.
 * You may also obtain a copy of the License at this URL:
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @copyright  Copyright (c) 2010, Jonathan Hedrén
 * @author     Jonathan Hedrén <jonathan.hedren@gmail.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */

/**
 * Represents a PDO database transaction.
 */
class PdoDbTransaction
{
    /**
     * The PDO connection.
     *
     * @var PDO
     */
    protected $connection;

    /**
     * The queries in the transaction.
     *
     * @var array
     */
    protected $queries = array();

    /**
     * The values of the queries.
     *
     * @var array
     */
    protected $values = array();

    /**
     * Constructor.
     *
     * @param PDO $connnection
     * @return PdoDbTransaction
     */
    public function __construct(PDO $connnection)
    {
        $this->connection = $connnection;
        return $this;
    }

    /**
     * Adds a query (with optional values) to the transaction.
     *
     * @param string $sql
     * @param string|array $values
     * @return PdoDbTransaction
     */
    public function addQuery($sql, $values = array())
    {
        if (!is_array($values)) {
            $values = array($values);
        }

        $this->queries[] = $sql;
        $this->values[] = $values;
        
        return $this;
    }

    /**
     * Commits the transaction.
     *
     * @return array|DbResult
     */
    public function commit()
    {
        if (!count($this->queries)) {
            throw new Exception('There are no SQL queries to commit.');
        }

        if (count($this->queries) > 1) {
            try {
                $this->connection->setAttribute(PDO::ATTR_AUTOCOMMIT, false);
            } catch (PDOException $e) {
                
            }
        }

        try {
            $this->connection->beginTransaction();

            $results = array();
            foreach ($this->queries as $index => $sql) {

// TODO: Query caching?
//        if (!preg_match('/\b(?:INSERT|UPDATE|REPLACE|SET)\b/i', $sql)) {
//            $hash = md5($sql . implode('', $values));
//            if (!isset($this->resultCache[$hash])) {
//                $this->resultCache[$hash] = $this->doQuery($sql, $values);
//            }
//            return $this->resultCache[$hash];
//        }

                $statement = $this->prepareStatement($sql, $this->values[$index]);
                $statement->execute();

                $results[] = $this->populateResult($statement);

                $statement->closeCursor();
            }

            $this->connection->commit();

            return (count($results) > 1) ? $results : $results[0];

        } catch(PDOException $e) {
            $this->connection->rollback();
            throw $e;
        }
    }

    /**
     * Populates a DbResult object.
     *
     * @param PDOStatement $statement
     * @return DbResult
     */
    protected function populateResult(PDOStatement $statement)
    {
        $result = new DbResult();
        $result->setRowSet($statement->fetchAll(PDO::FETCH_OBJ))
               ->setLastInsertId($this->connection->lastInsertId())
               ->setAffectedRows($statement->rowCount());
        return $result;
    }

    /**
     * Prepares and executes a PDO statement.
     *
     * @param string $sql
     * @param array $values
     * @return PDOStatement
     */
    protected function prepareStatement($sql, array $values)
    {
        $statement = $this->connection->prepare($sql);

        if (count($values)) {
            $i = 1;
            foreach ($values as $value) {
                if (is_integer($value)) {
                    $dataType = PDO::PARAM_INT;
                } else if ($value === null){
                    $dataType = PDO::PARAM_NULL;
                } else {
                    $dataType = PDO::PARAM_STR;
                }
                $statement->bindValue($i++, $value, $dataType);
            }
        }
        return $statement;
    }
}